/*
 * xml.c
 *
 *  Created on: February 18, 2012
 *      Author: Charbel Choueiri
 */

#include <stdlib.h>
#include "common.h"

XmlNode* XmlCreateNode(int32 iType, String* sName, String* sValue)
{
	XmlNode* oNode = (XmlNode*)calloc(sizeof(XmlNode), 1);
	oNode->nodeType = iType;
	oNode->nodeName = sName;
	oNode->nodeValue = sValue;
	return oNode;
}

/**
 * Frees the memory used by the node. Removes the node from it's parent and deletes it's attributes.
 * If delete children is specified then the children tree will be deleted as well.
 */
void XmlDeleteNode(XmlNode* pNode, bool bDeleteChildren)
{
	//Remove the node from parent if parent nod exists.
	if (pNode->parentNode != null)
	{
		//If node is an attribute then remove attribute node.
		if (pNode->nodeType == ATTRIBUTE_NODE) XmlRemoveAttributeNode(pNode->parentNode, pNode);
		//Else remove child node.
		else XmlRemoveChild(pNode->parentNode, pNode);
	}

	//Delete the node children if the "bDeleteChildren" boolean is true.
	if (bDeleteChildren) while (pNode->firstChild != null) XmlDeleteNode(pNode->firstChild, true);

	//Delete the node attributes if they exists.
	while (pNode->firstAttribute != null) XmlDeleteNode(pNode->firstAttribute, true);

	//Delete the node name and value.
	if (pNode->nodeName != null) StringDelete(pNode->nodeName);
	if (pNode->nodeValue != null) StringDelete(pNode->nodeValue);

	//Free the node memory.
	free(pNode);
}

/**
 * Append the child to the parent. If the child was already part of a different parent it
 * is removed first.
 */
XmlNode* XmlAppendChild(XmlNode* pParent, XmlNode* pChild)
{
	return XmlAppendNode(pParent, pChild, &(pParent->firstChild), &(pParent->lastChild));
}

/**
 * Removes the child from the parent.
 */
XmlNode* XmlRemoveChild(XmlNode* pParent, XmlNode* pChild)
{
	return XmlRemoveNode(pParent, pChild, &(pParent->firstChild), &(pParent->lastChild));
}

/**
 * Insert the child to the parent in the position before the "beforeCHild
 */
XmlNode* XmlInsertChild(XmlNode* pParent, XmlNode* pNewChild, XmlNode* pBeforeChild)
{
	//If no before child is specified then append to the end of linked list.
    if(pBeforeChild == null) XmlAppendChild(pParent, pNewChild);

    //Otherwise if before child does not belong to the parent then do nothing.
    else if(pBeforeChild->parentNode != pParent) return null;

    //If new child is part of a parent then remove it first.
	if(pNewChild->parentNode != null) XmlRemoveChild(pNewChild->parentNode, pNewChild);

	//Append the new child to the parent.
	pNewChild->parentNode = pParent;
	pNewChild->nextSibling = pBeforeChild;
	pNewChild->previousSibling = pBeforeChild->previousSibling;

	//If before child is the first child then append new child as first child.
	if(pParent->firstChild == pBeforeChild)  pParent->firstChild = pNewChild;
	//Otherwise add the new child before the before child.
	else pBeforeChild->previousSibling->nextSibling = pNewChild;

	//Update the previous pointer to the new child.
	pBeforeChild->previousSibling = pNewChild;

	//Return the new child.
    return pNewChild;
}

/**
 * Replaces the old child with the new child within the parent.
 */
XmlNode* XmlReplaceChild(XmlNode* pParent, XmlNode* pNewChild, XmlNode* pOldChild)
{
	//If the old child is not part of the parent then do nothing.
    if(pOldChild->parentNode != pParent) return null;

    //If new child is part of a parent then remove it first.
    if(pNewChild->parentNode != null) XmlRemoveChild(pNewChild->parentNode, pNewChild);

    //Swap the old child links to the new child.
	pNewChild->nextSibling = pOldChild->nextSibling;
	pNewChild->parentNode = pOldChild->parentNode;
	pNewChild->previousSibling = pOldChild->previousSibling;

	//If the old child is the first or last child of the parent then update the parent links as well.
	if(pParent->lastChild == pOldChild) pParent->lastChild = pNewChild;
	if(pParent->firstChild == pOldChild) pParent->firstChild = pNewChild;

	//Return the old child.
    return pOldChild;
}

void XmlRemoveAttribute(XmlElement* pElement, String* sName)
{
	XmlAttribute* pAttribute = XmlGetAttributeNode(pElement, sName);
	if (pAttribute != null) XmlDeleteNode(pAttribute, true);
}

XmlAttribute* XmlRemoveAttributeNode(XmlElement* pElement, XmlAttribute* pAttribute)
{
	return XmlRemoveNode(pElement, pAttribute, &(pElement->firstAttribute), &(pElement->lastAttribute));
}

const String* XmlGetAttribute(XmlElement* pElement, String* sName)
{
    XmlAttribute* pAttribute = XmlGetAttributeNode(pElement, sName);
    return (pAttribute == null ? null : pAttribute->nodeValue);
}

XmlAttribute* XmlGetAttributeNode(XmlElement* pElement, String* sName)
{
    XmlAttribute* pAttribute = pElement->firstAttribute;
    int32 iHashcode = StringHashCode(sName);

    while(pAttribute != null)
    {
        if(StringHashCode(pAttribute->nodeName) == iHashcode) break;
        pAttribute = pAttribute->nextSibling;
    }

    return pAttribute;
}

XmlAttribute* XmlSetAttribute(XmlElement* pElement, String* sName, String* sValue)
{
	//Check if attribute with the same name already exists.
	XmlAttribute* pAttribute = XmlGetAttributeNode(pElement, sName);

	//If attribute does not already exist then create it and add it.
	if (pAttribute == null)
	{
		//Create the attribute.
		pAttribute = XmlCreateNode(ATTRIBUTE_NODE, sName, sValue);

		//Add it to the element.
		XmlAppendNode(pElement, pAttribute, &(pElement->firstAttribute), &(pElement->lastAttribute));
	}
	//Otherwise change the value of the attribute the new value.
	else
	{
		//Change the value of the string to the new string.
		StringDelete(pAttribute->nodeValue);
		pAttribute->nodeValue = sValue;
	}

	return null;
}

XmlAttribute* XmlSetAttributeNode(XmlElement* pElement, XmlAttribute* pAttribute)
{
	//If there is already an attribute with the same name then remove it first.
    XmlRemoveAttribute(pElement, pAttribute->nodeName);

    //Append the new attribute to the element.
    XmlAppendNode(pElement, pAttribute, &(pElement->firstAttribute), &(pElement->lastAttribute));

    //Return the appended attribute.
    return pAttribute;
}

/**
 * Appends the node the parent using the first and last nodes as the linked list header and footer.
 * This is a helper function reduce code redundancy for append child and append attribute.
 */
XmlNode* XmlAppendNode(XmlNode* pParent, XmlNode* pNode, XmlNode** pFirst, XmlNode** pLast)
{
	//If child belongs to another parent then remove it first. If child is already
	//part of this parent then remove it anyway and append back at the bottom.
    if(pNode->parentNode != null) XmlRemoveNode(pParent, pNode, pFirst, pLast);

    //Append the child to the parent.
    //If parent has not children then append child to the top.
    if(*pFirst == null)
    {
    	pNode->previousSibling = null;
    	*pFirst = pNode;
    }
    //Otherwise append the child to the end of the parent's children linked list.
    else
    {
    	pNode->previousSibling = *pLast;
    	(*pLast)->nextSibling = pNode;
    }

    //Set the child's links to parent and siblings.
    pNode->nextSibling = null;
    pNode->parentNode = pParent;
    *pLast = pNode;

    //Return the append child.
    return pNode;
}

XmlNode* XmlRemoveNode(XmlNode* pParent, XmlNode* pNode, XmlNode** pFirst, XmlNode** pLast)
{
	//If child is not part of this parent then do nothing.
	if (pNode->parentNode != pParent) return null;

	//Remove the child from the parent.
	//If the child is the first in the parent's child linked list.
	if (*pFirst == pNode)
	{
		//If this is the only child in the linked list then clear both first and last.
		if (*pLast == pNode)
		{
			*pLast = null;
			*pFirst = null;
		}
		//Otherwise remove it and make the next sibling the first child.
		else
		{
			*pFirst = pNode->nextSibling;
			(*pFirst)->previousSibling = null;

		}
	}
	//Else if the child is the last child.
	else if (*pLast == pNode)
	{
		pNode->previousSibling->nextSibling = null;
		*pLast = pNode->previousSibling;
	}
	//Otherwise remove the child from somewhere in the middle.
	else
	{
		//Link the previous sibling to the next sibling and vis-versa.
		pNode->previousSibling->nextSibling = pNode->nextSibling;
		pNode->nextSibling->previousSibling = pNode->previousSibling;
	}

	//Clear the child's links to parent and siblings.
	pNode->parentNode = null;
	pNode->nextSibling = null;
	pNode->previousSibling = null;

	//Return the removed child.
	return pNode;
}


















